/////////////////////////////////////////////////////////////////////////////////////
// Include Files
#include "Systems\Renderer.h"

// managers
#include "Managers\TextureManager.h"
#include "Managers\ShaderManager.h"
#include "Managers\MeshManager.h"

// systems
#include "Systems\Window.h"

// rendering
#include "Rendering\Camera.h"
#include "Rendering\RenderState.h"

// HACK?
#include "Main\Game.h"

#include "Cg\CgGL.h"

// TODO: 4x anti-aliasing, 8x anisotropic filtering, set in options?

/////////////////////////////////////////////////////////////////////////////////////
// ISingleton<CRenderer> Static Initialization
CRenderer * ISingleton<CRenderer>::m_pInstance = CE_NULL;

/////////////////////////////////////////////////////////////////////////////////////
// CRenderer Implementation
CRenderer::CRenderer() { }

void CRenderer::Initialize()
{
	// create camera
	m_pCamera = new CCamera();
	m_pCamera->Translate(0.0f, 0.0f, -30.0f);
	m_pCamera->RotateViewAroundOrigin(0.3f, 0.7f);

	// set the OpenGL viewport to the correct size
	ReSizeGLScene(800, 600);

	// start the process, no limit to frame rate because vsync is on
	StartProcess(0);
}

void CRenderer::Shutdown()
{
	// stop the process
	StopProcess();

	delete m_pCamera;
	m_pCamera = CE_NULL;
}

void CRenderer::OnMeshLoad( CMeshEventArg &_rArg )
{
	m_pMeshManager->CreateMesh(_rArg);
}

void CRenderer::OnShaderLoad( CShaderLoadEventArg &_rArg )
{
	m_pShaderManager->CreateShader(_rArg);
}

void CRenderer::OnTextureLoad( CTextureEventArg &_rArg )
{
	m_pTextureManager->CreateTexture(_rArg);
}

void CRenderer::OnRenderStateAdd( CRenderStateEventArg &_rArg )
{
	CRenderState *pState = _rArg.GetRenderState();
	pState->AddRef();

	for( uint_t i = 0; i < m_lstRenderStates.GetSize(); ++i )
	{
		if( m_lstRenderStates[i] == CE_NULL )
		{
			m_lstRenderStates[i] = pState;
			return;
		}
	}
	m_lstRenderStates.Append(pState);
}

void CRenderer::OnRenderStateRemove( CRenderStateEventArg &_rArg )
{
	CRenderState *pState = _rArg.GetRenderState();
	pState->Release();

	for( uint_t i = 0; i < m_lstRenderStates.GetSize(); ++i )
	{
		if( m_lstRenderStates[i] == pState )
		{
			m_lstRenderStates[i] = CE_NULL;
			return;
		}
	}
}

void CRenderer::OnRenderStateUpdate( CRenderStateEventArg &_rArg )
{
	(void)_rArg;
	// TODO
}

void CRenderer::OnMeshUpdate( CMeshEventArg &_rArg )
{
	m_pMeshManager->UpdateMesh(_rArg);
}

void CRenderer::ProcessBegin()
{
	int_t iPixelFormat;

	// create the desired pixel format
	PIXELFORMATDESCRIPTOR pfd = {
		sizeof(PIXELFORMATDESCRIPTOR),            // nSize
		1,                                        // nVersion
		PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | 
		PFD_DOUBLEBUFFER,                         // dwFlags
		PFD_TYPE_RGBA,                            // iPixelType
		32,                                       // cColorBits
		0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
		16,                                       // cDepthBits
		0,                                        // cStencilBits
		0,                                        // cAuxBuffers
		PFD_MAIN_PLANE,                           // iLayerType
		0, 0, 0, 0
	};

	// get the device context
	m_hGLDevice = GetDC(m_pWindow->GetContext());
	assert(m_hGLDevice);

	// find the best matching pixel format
	iPixelFormat = ChoosePixelFormat( m_hGLDevice, &pfd );
	assert(iPixelFormat);

	// set the pixel format
	if( !SetPixelFormat(m_hGLDevice, iPixelFormat, &pfd) )
	{
		assert(false);
	}

	// create a temporary OpenGL 1.2 context
	HGLRC tempContext = wglCreateContext(m_hGLDevice);
	if( !wglMakeCurrent(m_hGLDevice, tempContext) )
	{
		assert(false);
	}

	// create the desired attributes
	int_t pf_attr[] = {
		WGL_ACCELERATION,   WGL_FULL_ACCELERATION, 
		WGL_DRAW_TO_WINDOW, GL_TRUE, 
		WGL_SUPPORT_OPENGL, GL_TRUE, 
		WGL_DOUBLE_BUFFER,  GL_TRUE, 
		WGL_PIXEL_TYPE,     WGL_TYPE_RGBA, 
		WGL_COLOR_BITS,     32, 
		WGL_DEPTH_BITS,     32, 
		0
	};

	GLExtInit();

	uint_t numformats;

	wglChoosePixelFormat(m_hGLDevice, pf_attr, NULL, 1, &iPixelFormat, &numformats);

	m_pWindow->Destroy();
	m_pWindow->Create();
	m_pWindow->Show();
	m_hGLDevice = GetDC(m_pWindow->GetContext());
	assert(m_hGLDevice);

	if( !SetPixelFormat(m_hGLDevice, iPixelFormat, &pfd) )
	{
		assert(false);
	}

	// TODO: forward compatibility and version 3.2 don't seem to work with Cg, fuck
	//     it, we're going all out OpenGL shaders.
	int_t ctx_attr[] = {
		WGL_CONTEXT_MAJOR_VERSION, 3, 
		WGL_CONTEXT_MINOR_VERSION, 1, 
		//WGL_CONTEXT_FLAGS, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT, 
		//WGL_CONTEXT_PROFILE_MASK, WGL_CONTEXT_CORE_PROFILE_BIT, 
		0
	};

	// create the final OpenGL context
	m_hGLRender = wglCreateContextAttribs(m_hGLDevice, 0, ctx_attr);
	assert(m_hGLRender);

	// delete the temporary context
	if( !wglDeleteContext(tempContext) )
	{
		assert(false);
	}

	// use the new context
	if( !wglMakeCurrent( m_hGLDevice, m_hGLRender ) )
	{
		assert(false);
	}

	// listen for asset load events
	AddEventListener(m_pMeshManager->MeshLoad, &CRenderer::OnMeshLoad);
	AddEventListener(m_pMeshManager->MeshUpdate, &CRenderer::OnMeshUpdate);
	AddEventListener(m_pShaderManager->ShaderLoad, &CRenderer::OnShaderLoad);
	AddEventListener(m_pTextureManager->TextureLoad, &CRenderer::OnTextureLoad);

	// listen for render state events from the game
	AddEventListener(m_pGame->RenderStateAdd, &CRenderer::OnRenderStateAdd);
	AddEventListener(m_pGame->RenderStateRemove, &CRenderer::OnRenderStateRemove);
}

void CRenderer::ProcessEnd()
{
	// stop listening for render state events
	RemoveEventListener(m_pGame->RenderStateRemove, &CRenderer::OnRenderStateRemove);
	RemoveEventListener(m_pGame->RenderStateAdd, &CRenderer::OnRenderStateAdd);

	// stop listening for asset load events
	RemoveEventListener(m_pTextureManager->TextureLoad, &CRenderer::OnTextureLoad);
	RemoveEventListener(m_pShaderManager->ShaderLoad, &CRenderer::OnShaderLoad);
	RemoveEventListener(m_pMeshManager->MeshUpdate, &CRenderer::OnMeshUpdate);
	RemoveEventListener(m_pMeshManager->MeshLoad, &CRenderer::OnMeshLoad);

	// take care of any left over events
	// TODO: is this a good thing or should I do something different?
	ProcessEvents();

	// destroy the OpenGL context
	if( !wglMakeCurrent( CE_NULL, CE_NULL ) )
	{
		assert(false);
	}

	if( !wglDeleteContext(m_hGLRender) )
	{
		assert(false);
	}
	m_hGLRender = CE_NULL;
}

void CRenderer::ProcessTick( float_t _fTime )
{
	(void)_fTime;

	// check for errors
#ifdef _DEBUG
	GLenum eError2 = glGetError();
	assert( eError2 == GL_NO_ERROR );
#endif // _DEBUG

	// TODO: gotta rename this, too much called "Process"
	ProcessEvents();

	// check for errors
#ifdef _DEBUG
	GLenum eError3 = glGetError();
	assert( eError3 == GL_NO_ERROR );
#endif // _DEBUG

	/////////////////////////////////////////////////////////////////////////////
	// HACK: camera movement

	// move the camera around the origin
	float_t fPosX = 0.0f, fPosY = 0.0f, fPosZ = 0.0f;
	if( GetAsyncKeyState(VK_RIGHT) & 0x8000 ) fPosX += 0.523598f * _fTime;
	if( GetAsyncKeyState(VK_LEFT ) & 0x8000 ) fPosX -= 0.523598f * _fTime;
	if( GetAsyncKeyState(VK_UP   ) & 0x8000 ) fPosY += 0.523598f * _fTime;
	if( GetAsyncKeyState(VK_DOWN ) & 0x8000 ) fPosY -= 0.523598f * _fTime;
	if( GetAsyncKeyState(VK_PRIOR) & 0x8000 ) fPosZ += 30.0f * _fTime;
	if( GetAsyncKeyState(VK_NEXT ) & 0x8000 ) fPosZ -= 30.0f * _fTime;
	m_pCamera->RotateViewAroundOrigin(-fPosX, fPosY); // TODO: RotateViewArountPoint (sphere)
	m_pCamera->Translate(0.0f, 0.0f, -fPosZ);

	// limit zoom
	if( m_pCamera->m_matTransform.arr[14] < 30.0f )  m_pCamera->m_matTransform.arr[14] = 30.0f;
	if( m_pCamera->m_matTransform.arr[14] > 400.0f ) m_pCamera->m_matTransform.arr[14] = 400.0f;

	// END HACK: camera movement
	/////////////////////////////////////////////////////////////////////////////

	// clear buffers
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	// draw all render states TODO: remove Render() from CRenderState, do it here
	matrix_t matView = m_pCamera->GetTransform();
	matrix_t matProj = m_pCamera->GetProjection();

	for( uint_t i = 0; i< m_lstRenderStates.GetSize(); ++i )
	{
		if( m_lstRenderStates[i] )
		{
			m_lstRenderStates[i]->Render(matView, matProj);
		}
	}

	// show the new frame
	SwapBuffers(m_hGLDevice);

#ifdef _DEBUG
	// make sure there have not been any errors
	GLenum eError = glGetError();
	assert(eError == GL_NO_ERROR);
#endif _DEBUG
}

void CRenderer::ReSizeGLScene(uint_t width, uint_t height)
{
	m_pCamera->SetPerspective( 45.0f, (float_t)width / height, 0.1f, 500.0f );
}
